﻿using AutoMapper;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Sino.CapacityCloud.Infrastructure.Features;
using Sino.CapacityCloud.Repositories.Models;
using Sino.CapacityCloud.Services;
using Sino.CapacityCloud.WebApi.ViewModels;
using Sino.Tms.Primitives;
using Sino.Tms.WebApi.Framework.Primitives;
using Sino.WebApi.Framework.Core.Filters;
using Sino.WebApi.Framework.Responses;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Z.EntityFramework.Plus;

namespace Sino.CapacityCloud.WebApi.ApiControllers
{

    public partial class UserController
        : ApiController<UserController>
    {

        public async Task<JsonResult> GetUserRoles()
        {
            try
            {
                var options = new MemoryCacheEntryOptions() { SlidingExpiration = TimeSpan.FromHours(2) };
                var response = new GetUserRolesResponse()
                {
                    UserRoles = this.DataContext.Roles
                        .Select(x => new RoleViewModel() { Id = x.Id, Name = x.Name })
                        .FromCache(options).ToArray()
                };
                return await this.PackageResultAsync<GetUserRolesResponse>(response);
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get your user info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<GetUserRolesResponse>(ex);
            }
        }

        public async Task<JsonResult> GetUserRating(int goodRating, string userId, int startIndex, int count)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    if (user.Car == null)
                    {
                        user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
                    }
                    if (user.Car == null)
                    {
                        return await this.PackageResultAsync<MyRatingInfoResponse>();
                    }

                    var ratingsResponse = await this._tmsService.QueryEvaluationsAsync(null, user.Id);
                    var ratings = ratingsResponse?.Output?.List;

                    if (ratings != null && ratings.Count() > 0)
                    {
                        var list = ratings.Select(x => new OrderRatingViewModel()
                        {
                            Date = SystemUtils.Try(() => DateTime.Parse(x.CreationTime)),
                            Attitude = x.Attitude,
                            Integrity = x.Credibility,
                            Timeliness = x.Timeliness,
                            TransPrice = x.Freight,
                            Comment = x.Content,
                            Name = null,
                            TotalPoint = (x.Attitude + x.Credibility + x.Timeliness + x.Freight) / 4.0f
                        });
                        var GoodRatingCount = list.Count(x => x.TotalPoint >= 8);
                        var total = list.Count();
                        if (goodRating == 1)
                        {
                            list = list.Where(x => x.TotalPoint >= 8);
                        }
                        else if (goodRating == 2)
                        {
                            list = list.Where(x => x.TotalPoint < 8);
                        }
                        var response = new PagedRatingsResponse()
                        {
                            List = list.Skip(startIndex).Take(count).ToList(),
                            Total = total,
                            Average = list.Average(x => x.TotalPoint),
                            GoodRatingCount = GoodRatingCount
                        };
                        return await this.PackageResultAsync<PagedRatingsResponse>(response);
                    }
                    return await this.PackageResultAsync<PagedRatingsResponse>();
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get your rating info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<PagedRatingsResponse>(ex);
            }
        }

        public async Task<JsonResult> CreateUser(string email, string phoneNumber)
        {
            try
            {
                if (this.DataContext.Users.Any(x => x.PhoneNumber == phoneNumber))
                {
                    throw new ArgumentException($"The given user phone number {phoneNumber} has been used.");
                }
                // If the user is not found, then we should register him or her fist.
                var password = await this._randomPasswordService.GenerateRandomPasswordAsync();
                var user = new ApplicationUser()
                {
                    PhoneNumber = phoneNumber,
                    UserName = email,
                    Email = email,
                    CertificatePhotoCertificationState = CertificationStatus.Uncommitted,
                    DrivingLicenseKeyCertificationState = CertificationStatus.Uncommitted,
                    IdentityCardCertificationState = CertificationStatus.Uncommitted
                };
                var identityResult = await this.UserManager.CreateAsync(user, password);
                if (!identityResult.Succeeded)
                {
                    throw new SystemException($"Failed to set role to the user {phoneNumber} with: \n{string.Join("\n", identityResult.ToErrorStrings())}.");
                }
                if (!await this.RoleManager.RoleExistsAsync(ApplicationRole.Driver))
                {
                    this._logger.LogWarning($"Missing '{ApplicationRole.Driver}' role. Adding '{ApplicationRole.Driver}' role...");
                    identityResult = await this.RoleManager.CreateAsync(new ApplicationRole(ApplicationRole.Driver));
                    if (!identityResult.Succeeded)
                    {
                        throw new SystemException($"Failed to add '{ApplicationRole.Driver}' role with: \n{string.Join("\n", identityResult.ToErrorStrings())}.");
                    }
                }
                identityResult = await this.UserManager.AddToRoleAsync(user, ApplicationRole.Driver);
                if (!identityResult.Succeeded)
                {
                    throw new SystemException($"Failed to add user {user.Id} to '{ApplicationRole.Driver}' role with: \n{string.Join("\n", identityResult.ToErrorStrings())}.");
                }
                var response = new UserInfoResponse()
                {
                    Info = new UserInfo()
                    {
                        UserId = user.Id,
                        UserName = user.PhoneNumber,
                        RealNameState = (RealNameState)user.IdentityCardCertificationState,
                        RealName = user.RealName,
                        HeadImage = user.AvatarKey,
                        Address = user.Address,
                        Point = user.Points.FirstOrDefault(x => x.UserId == user.Id)?.TotalPoints ?? 0.0f,
                        Profile = user.Profile
                    }
                };
                return await this.PackageResultAsync<UserInfoResponse>(response);
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to create user [{email}, {phoneNumber}] because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<GetUserRolesResponse>(ex);
            }
        }

        [SupressUserLogin]
        public async Task<JsonResult> GetUsers(string keyword, string realName, string phoneNumber, string carCode, DateTime? startTime, DateTime? endTime, DateTime? applicationStartTime, DateTime? applicationEndTime, int isEnabled, CertificationStatus state, bool useCache = false, string orderBy = null, int acs = 0, int startIndex = 0, int count = 10)
        {
            try
            {
                if (endTime < startTime)
                {
                    throw new ArgumentOutOfRangeException("EndTime should be greater than StartTime.");
                }

                var options = new MemoryCacheEntryOptions() { SlidingExpiration = TimeSpan.FromSeconds(20) };

                IQueryable<ApplicationUser> query = this.DataContext.Users
                    .AsNoTracking()
                    .Include(x => x.Car);
                    //.Include(x => x.Points)
                    //.Include(x => x.Roles)
                    //.Where(x => x.LockoutEnabled == (isEnabled != 1));

                // 1：true，2：false，0：Null
                switch (isEnabled)
                {
                    case 2:
                    case 1:
                        query = query.Where(x => (!x.LockoutEnabled) == (isEnabled != 2));
                        break;
                    case 0:
                    default:
                        break;
                }

                if (!string.IsNullOrWhiteSpace(keyword))
                {
                    query = query.Where(x => x.UserName.Contains(keyword) ||
                        x.PhoneNumber.Contains(keyword) ||
                        x.RealName.Contains(keyword) ||
                        (x.Car != null && x.Car.Code.Contains(keyword)));
                }

                if (!string.IsNullOrWhiteSpace(phoneNumber))
                {
                    query = query.Where(x => x.PhoneNumber.Contains(phoneNumber));
                }

                if (!string.IsNullOrWhiteSpace(realName))
                {
                    query = query.Where(x => x.RealName.Contains(realName));
                }

                if (!string.IsNullOrWhiteSpace(carCode))
                {
                    query = query.Where(x => x.Car != null && x.Car.Code.Contains(carCode));
                }

                if (startTime != null)
                {
                    query = query.Where(x => x.CertificationTime >= startTime.Value);
                }

                if (endTime != null)
                {
                    query = query.Where(x => x.CertificationTime < endTime.Value);
                }

                if (applicationStartTime != null)
                {
                    query = query.Where(x => x.CertificationApplicationTime >= applicationStartTime.Value);
                }

                if (applicationEndTime != null)
                {
                    query = query.Where(x => x.CertificationApplicationTime < applicationEndTime.Value);
                }

                if (!string.IsNullOrWhiteSpace(orderBy))
                {
                    query = query.OrderBy(orderBy, acs != 1);
                }

                IEnumerable<ApplicationUser> list;
                if (useCache)
                {
                    list = query.FromCache(options);
                }
                else
                {
                    list = query.ToList();
                }

                list = list.Select(x =>
                {
                    x.OverallCertificateStatus = (CertificationStatus)x.GetCertificateStatus();
                    return x;
                }).OrderByDescending(x => x.OverallCertificateStatus == CertificationStatus.Pending ? 1 : 0);

                if (state != CertificationStatus.None)
                {
                    list = list.Where(x => x.OverallCertificateStatus == state);
                }

                var response = new OutputVmResponse<PageVM<UserInfoListItemVM>>()
                {
                    Output = new PageVM<UserInfoListItemVM>()
                    {
                        List = list.Skip(startIndex).Take(count).Select(x => new UserInfoListItemVM()
                        {
                            UserId = x.Id,
                            UserName = x.PhoneNumber,
                            RealNameState = (RealNameState)x.OverallCertificateStatus,
                            RealName = x.RealName,
                            AvatarUri = x.AvatarKey,
                            Address = x.Address,
                            Point = 0.0f, // x.Points.LastOrDefault()?.TotalPoints ?? 0.0f,
                            Profile = x.Profile,
                            IsLockedout = !x.LockoutEnabled,
                            CarNumber = x.Car?.Code,
                            CarLength = x.Car?.Length ?? CarLength.None,
                            CarType = x.Car?.Type ?? VehicleType.None,
                            AuthorizedLoad = x.Car?.AssignmentOfLoad ?? 0.0,
                            CarriageType = x.CarriageType,
                            Roles = new string[0], // x.Roles.Select(z => z.RoleId).ToArray(),
                            PhoneNumber = x.PhoneNumber,
                            CertificationTime = x.CertificationTime,
                            CertificationApplicationTime = x.CertificationApplicationTime
                        }),
                        Total = list.Count()
                    }
                };
                return await this.PackageResultAsync(response);
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get your user info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<GetUserRolesResponse>(ex);
            }
        }

        public async Task<JsonResult> GetUserBasicInfo(string userId)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    if (user.Car == null) user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
                    //if (user.Car == null)
                    //{
                    //    throw new SystemException($"Cannot find a car for the user {user.Id}.");
                    //}
                    var carLength = user.Car?.Length.GetCustomAttribute<CarLength, EnumTextMetaAttribute>(e => e == CarLength.None ? "" : null).Text;
                    try
                    {
                        var apiFeature = SystemUtils.Try(() => this.HttpContext.Features[typeof(IApiCompatibilityFeature)] as IApiCompatibilityFeature);
                        if (apiFeature != null)
                        {
                            if (!string.IsNullOrWhiteSpace(apiFeature.ApiVersion) && new Version(apiFeature.ApiVersion) == new Version("1.0.0"))
                            {
                                carLength = carLength.Replace("6米", "6.0米");
                            }
                        }
                    }
                    catch
                    {
                        carLength = "";
                    }
                    var response = new UserInfoResponse()
                    {
                        Info = new UserInfo()
                        {
                            UserId = user.Id,
                            UserName = user.PhoneNumber,
                            PhoneNumber = user.PhoneNumber,
                            RealNameState = user.GetCertificateStatus(),
                            RealName = user.RealName,
                            HeadImage = user.AvatarKey,
                            Address = user.Address,
                            Point = user.Points.FirstOrDefault(x => x.UserId == user.Id)?.TotalPoints ?? 0.0f,
                            Profile = user.Profile,
                            CarNumber = user.Car?.Code,
                            CarLength = carLength,
                            CarType = user.Car?.Type.GetCustomAttribute<VehicleType, EnumTextMetaAttribute>(e => e == VehicleType.None ? "" : null).Text,
                            AuthorizedLoad = user.Car?.AssignmentOfLoad ?? 0.0,
                            CarriageType = (CarriageType)user.CarriageType
                        }
                    };
                    return await this.PackageResultAsync<UserInfoResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get your user info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<UserInfoResponse>(ex);
            }
        }

        public async Task<JsonResult> GetUserRealNameInfo(string userId)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    if (user.Car == null) user.Car = this.DataContext.Cars.AsNoTracking().FirstOrDefault(x => x.CarId == user.CarId);
                    var overallState = user.GetCertificateStatus();
                    var response = new RealNameInfoResponse()
                    {
                        Info = new RealNameInfo()
                        {
                            State = overallState,
                            CarCode = user?.Car?.Code,
                            RealName = user?.RealName,
                            IDCardState = (RealNameState)user.IdentityCardCertificationState,
                            IDCardImage = user.IdentityCardKey,
                            DrivingLicenseState = (RealNameState)user.DrivingLicenseKeyCertificationState,
                            DrivingLicenseImage = user.DrivingLicenseKey,
                            VehicleLicenseState = (RealNameState)(user.Car?.CertificationStatus ?? CertificationStatus.Uncommitted),
                            VehicleLicenseImage = user.Car?.VehicleLicenseKey,
                            TrailerLicenseState = (RealNameState)(user.Car?.TrailerLicenseCertificationStatus ?? CertificationStatus.Uncommitted),
                            TrailerLicenseImage = user.Car?.TrailerLicenseKey,
                            VehiclePhotoState = (RealNameState)(user.Car?.VehiclePhotoCertificationStatus ?? CertificationStatus.Uncommitted),
                            VehiclePhotoImage = user.Car?.VehiclePhotoKey,
                            RealHeadState = (RealNameState)user.CertificatePhotoCertificationState,
                            RealHeadImage = user.CertificatePhotoKey,
                            Message = ""
                        }
                    };
                    return await this.PackageResultAsync<RealNameInfoResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to get your real name info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<RealNameInfoResponse>(ex);
            }
        }

        [HttpPost]
        public async Task<JsonResult> ModifyUserInfoByUserId(string userId, [FromBody]UserInfo info, bool forceEdit = false)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    await this.UpdateUserInfoAsync(user, info, forceEdit);
                    return await this.PackageResultAsync<VoidResponse>();
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to save user info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }

        [HttpPost]
        public async Task<JsonResult> ModifyRealNameInfoByUserId(string userId, RealNameType type, string imageUri, bool forceEdit)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(userId);
                if (user != null)
                {
                    var errorList = new List<string>();
                    try
                    {
                        this.UpdateUserRealNameInfo(user, type, imageUri, forceEdit);
                    }
                    catch (Exception ex)
                    {
                        errorList.Add(ex.Message + Environment.NewLine + ex.StackTrace);
                    }
                    var identityResult = await this.UserManager.UpdateAsync(user);
                    if (!identityResult.Succeeded)
                    {
                        var identityErrors = identityResult.Errors.Select(x => $"Code: {x.Code}, Description: {x.Description}").ToList();
                        errorList.AddRange(identityErrors);
                    }
                    var response = new PutRealNameInfoResponse()
                    {
                        IsSuccessed = errorList.Count == 0 && identityResult.Succeeded,
                        Message = string.Join(Environment.NewLine, errorList)
                    };
                    return await this.PackageResultAsync<PutRealNameInfoResponse>(response);
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to save real name info because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<PutRealNameInfoResponse>(ex);
            }
        }

        [HttpPost]
        public async Task<JsonResult> PutUserRealNameCertificationStatus([FromBody]RealNameInfoCertificationViewModel editVm)
        {
            try
            {
                var user = await this.UserManager.FindByIdAsync(editVm.UserId);
                if (user != null)
                {
                    user.IdentityCardCertificationState = editVm.IdCardKeyStatus;
                    user.CertificatePhotoCertificationState = editVm.RealHeadKeyStatus;
                    user.DrivingLicenseKeyCertificationState = editVm.DrivingLicenseKeyStatus;

                    if (user.Car == null)
                    {
                        user.Car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == user.CarId);
                    }

                    if (user.Car != null)
                    {
                        user.Car.CertificationStatus = editVm.VehicleLicenseKeyStatus;
                        user.Car.TrailerLicenseCertificationStatus = editVm.TrailerLicenseKeyStatus;
                        user.Car.VehiclePhotoCertificationStatus = editVm.VehiclePhotoKeyStatus;
                    }

                    if (user.GetCertificateStatus() == RealNameState.Pass)
                    {
                        user.CertificationTime = DateTime.Now;
                    }
                    else
                    {
                        user.CertificationTime = default(DateTime);
                    }

                    await this.UserManager.UpdateAsync(user);

                    return await this.PackageResultAsync<VoidResponse>();
                }
                throw new SystemException("Failed to find the specified ApplicationUser object.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to put user realname certification status because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<PutRealNameInfoResponse>(ex);
            }
        }

    }

}